package com.ysw.autoconfig;

import com.ysw.annotation.SoaProvider;
import com.ysw.annotation.SoaReference;
import com.ysw.annotation.SoaReferenceAnnotationUtil;
import com.ysw.proxy.SoaClientProxy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.annotation.InjectionMetadata;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.annotation.*;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName SoaReferenceAnnotationBeanPostProcessor
 * @Description TODO
 * @Author ysw
 * @Date 2021/10/29 21:53
 */
public class SoaReferenceAnnotationBeanPostProcessor implements MergedBeanDefinitionPostProcessor, InstantiationAwareBeanPostProcessor {

    protected final Logger logger = LoggerFactory.getLogger(SoaReferenceAnnotationBeanPostProcessor.class);

    @Autowired
    @Order(0)
    private RestTemplate restTemplate;

    /**
     * @SoaReference 注解收集
     */
    private final Map<String, InjectionMetadata> injectionMetadataCache = new ConcurrentHashMap<>(256);

    private final Set<Class<? extends Annotation>> soaAnnotationTypes = new LinkedHashSet<>(4);

    /**
     * 指定扫描类型@SoaReference
     * 后期扩展使用可以拔插形式
     */
    @SuppressWarnings("unchecked")
    public SoaReferenceAnnotationBeanPostProcessor() {
        this.soaAnnotationTypes.add(SoaReference.class);
        try {
            this.soaAnnotationTypes.add((Class<? extends Annotation>)
                    ClassUtils.forName("javax.inject.Inject", SoaReferenceAnnotationBeanPostProcessor.class.getClassLoader()));
            logger.trace("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");
        }
        catch (ClassNotFoundException ex) {
            // JSR-330 API not available - simply skip.
        }
    }

    /**
     * 完成注解的收集 @SoaReference注解的收集
     * 有点类似@Autwried 和 @Value 注解收集（AutowiredAnnotationBeanPostProcessor）
     *
     * @param rootBeanDefinition
     * @param beanType
     * @param beanName
     */
    @Override
    public void postProcessMergedBeanDefinition(RootBeanDefinition rootBeanDefinition, Class<?> beanType, String beanName) {
        //查找注解的元信息，传入了bean的名称、类型
        InjectionMetadata metadata = findSoaReferenceMetaData(beanName, beanType, null);
        metadata.checkConfigMembers(rootBeanDefinition);
    }

    @Override
    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        //元信息查找、解析
        InjectionMetadata metadata = findSoaReferenceMetaData(beanName, bean.getClass(), pvs);
        try {
            //进行注入 注入的主要逻辑
            metadata.inject(bean, beanName, pvs);
        } catch (BeanCreationException ex) {
            throw ex;
        } catch (Throwable ex) {
            throw new BeanCreationException(beanName, "Injection of @SoaReference dependencies failed", ex);
        }
        return pvs;
    }

    private InjectionMetadata findSoaReferenceMetaData(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
        // Fall back to class name as cache key, for backwards compatibility with custom callers.
        String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
        // Quick check on the concurrent map first, with minimal locking.
        InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
        if (InjectionMetadata.needsRefresh(metadata, clazz)) {
            synchronized (this.injectionMetadataCache) {
                metadata = this.injectionMetadataCache.get(cacheKey);
                if (InjectionMetadata.needsRefresh(metadata, clazz)) {
                    if (metadata != null) {
                        metadata.clear(pvs);
                    }
                    metadata = buildSoaReferenceMetadata(clazz);
                    this.injectionMetadataCache.put(cacheKey, metadata);
                }
            }
        }
        return metadata;
    }

    /**
     * 构造@SoaReference 注解元信息
     * @param clazz
     * @return
     */
    private InjectionMetadata buildSoaReferenceMetadata(Class<?> clazz) {
        if (!AnnotationUtils.isCandidateClass(clazz, this.soaAnnotationTypes)) {
            return InjectionMetadata.EMPTY;
        }
        List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
        Class<?> targetClass = clazz;
        do {
            final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
            ReflectionUtils.doWithLocalFields(targetClass, field -> {
                // 查找是否存在 @SoaReference 注解
                AnnotationAttributes ann = findSoaReferenceAnnotation(field);
                if (ann != null) {
                    if (Modifier.isStatic(field.getModifiers())) {
                        if (logger.isInfoEnabled()) {
                            logger.info("Autowired annotation is not supported on static fields: " + field);
                        }
                        return;
                    }
                    // SoaReferenceFieldInjectedElement 里面重新对inject方法进行了处理，就是在inject的时候产生代理对象
                    currElements.add(new SoaReferenceFieldInjectedElement(field, ann));
                }
            });
            elements.addAll(0, currElements);
            targetClass = targetClass.getSuperclass();
        }
        while (targetClass != null && targetClass != Object.class);

        return InjectionMetadata.forElements(elements, clazz);
    }

    /**
     * 判断是否添加了{@linkplain SoaReference}注解，是的话返回注解的{@linkplain AnnotationAttributes}，否则返回null
     * @param ao
     * @return
     */
    private AnnotationAttributes findSoaReferenceAnnotation(AccessibleObject ao) {
        // autowiring annotations have to be local
        if (ao.getAnnotations().length > 0) {
            AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(ao, SoaReference.class);
            if (attributes != null) {
                if(!StringUtils.hasText(SoaReferenceAnnotationUtil.getApplication(attributes))) {
                    throw new IllegalArgumentException("Injection of @SoaReference dependencies failed, application is empty, memberName:" + ao.toString());
                }

                return attributes;
            }
        }
        return null;
    }

    /**
     * 一个添加{@linkplain SoaReference}注解的成员变量注入元素，在给成员变量执行注入操作时，生成一个SoaClientProxy代理，并将该代理注入到成就变量中
     */
    private class SoaReferenceFieldInjectedElement extends InjectionMetadata.InjectedElement {
        String SOA_SERVICE_SUFFIX = "Service";
        private AnnotationAttributes soaReferenceAnnotationAttributes;

        public SoaReferenceFieldInjectedElement(Field field, AnnotationAttributes soaReferenceAnnotationAttributes) {
            super(field, null);
            this.soaReferenceAnnotationAttributes = soaReferenceAnnotationAttributes;
        }

        /**
         * spring bean成员变量执行注入操作，获取到成员变量的注入对象后，把该对象注入到成员变量中，最终该成员变量的地址就是指向了这个注入对象
         * @param bean spring bean实例
         * @param requestingBeanName bean名称
         * @param pvs
         * @throws Throwable
         */
        @Override
        protected void inject(Object bean, String requestingBeanName, PropertyValues pvs) throws Throwable {
            Field field = (Field) this.getMember();
            // 生成代理对象
            Object soaClientProxy = this.getSoaClientProxy(field);
            ReflectionUtils.makeAccessible(field);
            field.set(bean, soaClientProxy);
        }

        /**
         * 创建一个Soa代理
         * @param field
         * @return
         */
        private Object getSoaClientProxy(Field field) {
            String application = SoaReferenceAnnotationUtil.getApplication(this.soaReferenceAnnotationAttributes);
            Class serviceType = field.getType();
            String serviceName = this.getServiceName(field);
            //判断是否已经存在，不同的spring bean中注入相同的SoaReference的情况
            String cacheKey = application + "#" + serviceType.getName() + "#" + serviceName;
            SoaClientProxy soaClientProxy = new SoaClientProxy(application, serviceName, serviceType,restTemplate);
            return soaClientProxy.getProxy();
        }

        private String getServiceName(Field field) {
            AnnotationAttributes annotationAttributes = AnnotatedElementUtils.getMergedAnnotationAttributes(field.getType(), SoaProvider.class);
            String injectedObjectName = annotationAttributes != null ? annotationAttributes.getString("value") : null;

            if(!StringUtils.hasText(injectedObjectName)) {
                injectedObjectName = field.getType().getSimpleName();
                //如果取的是接口名称，则去掉最后的Service后缀
                if(injectedObjectName.endsWith(SOA_SERVICE_SUFFIX)) {
                    return injectedObjectName.substring(0, injectedObjectName.length() - SOA_SERVICE_SUFFIX.length());
                }
            }

            //将首字母转换成小写
            return injectedObjectName.substring(0, 1).toLowerCase() + injectedObjectName.substring(1);
        }

    }
}